﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Common.Utilities.Dispatcher;
using Common.Utilities.DLR;

namespace Workflows.Components.ETL
{
    /// <summary>
    /// 
    /// </summary>
    public class DataCleaningWorker:IWorker
    {
        private Thread _workerThread;
        private DataCleaningInstruction _instruction;
        private DataCleaningOutput _output;
        private Guid _jobTicketID;
        private RunContext _context;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }
        #region IWorker Members

        /// <summary>
        /// handler to worker thread
        /// </summary>
        public Thread WorkerThread
        {
            get { return _workerThread; }
        }

        /// <summary>
        /// allow instruction to be changed at runtime
        /// </summary>
        public InstructionBase Instruction
        {
            get { return _instruction; }
        }

        /// <summary>
        /// output
        /// </summary>
        public ResultBase Output
        {
            get { return _output; }
        }

        /// <summary>
        /// allow worker to be created based on component type
        /// </summary>
        public Type ComponentType
        {
            get { return typeof(DataCleaningComponent); }
        }

        /// <summary>
        /// runtime context retrieved from appl domain
        /// </summary>
        public RunContext Context
        {
            get { return _context; }
            set { _context = value; }
        }

        /// <summary>
        /// this must be called before actual work
        /// </summary>
        /// <param name="ticketGuid"></param>
        /// <param name="request"></param>
        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._jobTicketID = ticketGuid;
            this._instruction = (DataCleaningInstruction) request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        /// <summary>
        /// this is called within subflow, where runtime context is also initialized
        /// </summary>
        /// <param name="context"></param>
        /// <param name="ticketGuid"></param>
        /// <param name="request"></param>
        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._jobTicketID = ticketGuid;
            this._instruction = (DataCleaningInstruction)request;
            this._context = context;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        /// <summary>
        /// this is called synchronously, used in testing mode
        /// </summary>
        public void DoWork()
        {
            if(this.WorkerStartedEvent !=null)
            {
                this.WorkerStartedEvent(this._jobTicketID, this, this.GetType(), this._instruction);
            }
            this._workerThread = Thread.CurrentThread;
            this.EvaluteRules();
        }

        /// <summary>
        /// this is called by job dispatcher
        /// </summary>
        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._jobTicketID, this, this.GetType(), this._instruction);
            }
            this._workerThread = new Thread(new ThreadStart(this.EvaluteRules));
            this._workerThread.Start();
        }

        /// <summary>
        /// this is called by job monitor/manager
        /// </summary>
        public void CancelWork()
        {
            if(this._workerThread !=null && this._workerThread.IsAlive)
            {
                this._workerThread.Abort();
                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._jobTicketID, this.GetType(), this._instruction);
                }
            }
        }

        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        #endregion

        #region work
        private void EvaluteRules()
        {
            try
            {
                Dictionary<Guid, int> ruleIDsApplied=new Dictionary<Guid, int>();
                int recordValidated=0;
                int recordNotValidated = 0;

                Type itemType = Type.Missing.GetType();
                IEnumerator collection = this.CreateEnumeratorFromDataProvider(ref itemType);
                if(collection !=null)
                {
                    while(collection.MoveNext())
                    {
                        bool result = false;
                        object currentItem = collection.Current;
                        if(currentItem !=null)
                        {
                            Dictionary<string, object> inputs = new Dictionary<string, object>();
                            string inputName = itemType.Name + "1";
                            inputs.Add(inputName, currentItem);
                            foreach (DataCleaningRule rule in this._instruction.DataCleaningRules)
                            {
                                string className = "CodeEvaluator_" + rule.DataCleaningRuleID.ToString().Replace(".", "_");
                                object codeExecutionWorker = CodeEvaluation.CreateClassInMemory(className, rule.ExternalReferences,
                                                                                                inputs, rule.CodeSnippet);
                                result = CodeEvaluation.ExecuteCodeBlock(ref inputs, codeExecutionWorker);
                                if (result)
                                {
                                    if (ruleIDsApplied.ContainsKey(rule.DataCleaningRuleID))
                                    {
                                        int count = ruleIDsApplied[rule.DataCleaningRuleID];
                                        count++;
                                        ruleIDsApplied[rule.DataCleaningRuleID] = count;
                                    }
                                    else
                                    {
                                        ruleIDsApplied.Add(rule.DataCleaningRuleID, 1);
                                    }
                                    if (rule.TrueAction == DataFlowAction.BreakToEnd)
                                    {
                                        break;
                                    }
                                    else
                                    {
                                        continue;
                                    }
                                }
                                else
                                {
                                    if (rule.FalseAction == DataFlowAction.BreakToEnd)
                                        break;
                                    else
                                        continue;
                                }
                            }
                        }
                        if(result)
                        {
                            recordValidated++;
                        }
                        else
                        {
                            recordNotValidated++;
                        }
                    }
                }

                this._output = new DataCleaningOutput(this._instruction.OwnerComponentID, this._jobTicketID,
                                                      ruleIDsApplied, recordValidated, recordNotValidated);
                if(this.WorkerFinishedEvent !=null)
                {
                    this.WorkerFinishedEvent(this._jobTicketID, this.GetType(), this._instruction, this._output);
                }
            }
            catch(Exception ex)
            {
                if(this.WorkerErredEvent !=null)
                {
                    this.WorkerErredEvent(this._jobTicketID, this.GetType(), this._instruction, ex.ToString(),true);
                }
            }
        }

        private IEnumerator CreateEnumeratorFromDataProvider(ref Type itemType)
        {
            DataPipeProvider srcProvider = null;
            switch(this._instruction.SourceType)
            {
                case DataPipeSourceType.FileProvider:
                    srcProvider=new DataPipeProvider(this._instruction.RootFolderPath,this._instruction.FileFilter,this._instruction.FolderSearchOption);
                    break;
                case DataPipeSourceType.RowProvider:
                    if (!string.IsNullOrEmpty(this._instruction.TblName))
                    {
                        srcProvider = new DataPipeProvider(this._instruction.DbProvider, this._instruction.ConnStr,
                                                           this._instruction.TblName);
                    }
                    else 
                    {
                        srcProvider = new DataPipeProvider(this._instruction.DbProvider, this._instruction.ConnStr,
                                                           this._instruction.SelectSql, this._instruction.SortBy);
                    }
                    break;
                case DataPipeSourceType.TableProvider:
                    srcProvider = new DataPipeProvider(this._instruction.DbProvider, this._instruction.ConnStr,
                                                           this._instruction.SelectSql, this._instruction.GroupBy,
                                                           this._instruction.SortBy);
                    break;
            }
            if (srcProvider != null)
            {
                itemType = srcProvider.ItemType;
                return srcProvider.Collection;
            }
            else
                return null;
        }
        #endregion
    }
}
